package com.exam.api.config.interceptor;

import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.exam.api.modules.system.domain.TblRole;
import com.exam.api.modules.system.vo.LoginUserInfo;
import com.exam.api.annotation.DataScope;
import com.exam.api.common.exception.GlobalException;
import com.exam.api.modules.system.service.TblDeptService;
import com.exam.api.modules.system.service.TblRoleService;
import com.exam.api.modules.system.service.TblUserService;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.schema.Column;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
public class CustomDataPermissionHandler implements DataPermissionHandler {

    /**
     * @param where             原有的 where 条件信息
     * @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法
     * @return
     */
    @Override
    public Expression getSqlSegment( Expression where, String mappedStatementId) {
        // 不在上下文环境跳过
        if (!SpringMVCUtil.isWeb()) {
            // 跳过
            return where;
        }
//        if (!StpUtil.isLogin()) {
//            return where;
//        }
        // 获取是否有注解否则跳过
        Class<?> mapperClazz;
        try {
            // 获取mapper类
            mapperClazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(".")));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        // 获取方法名
        String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);

        /**
         * DataScope注解优先级：【类上 > 方法上】
         */
        // 获取 DataScope注解
        DataScope dataScopeAnnotationClazz = mapperClazz.getAnnotation(DataScope.class);
        if (ObjUtil.isNotNull(dataScopeAnnotationClazz) && dataScopeAnnotationClazz.enabled()) {
            log.debug("类上 where:{}  mappedStatementId:{}", where, mappedStatementId);
            return dataScopeFilter(where, dataScopeAnnotationClazz);
        }

        // 获取自身类中的所有方法，不包括继承。与访问权限无关
        Method[] methods = mapperClazz.getDeclaredMethods();
        for (Method method : methods) {
            DataScope dataScopeAnnotationMethod = method.getAnnotation(DataScope.class);
            if (ObjectUtil.isNull(dataScopeAnnotationMethod) || !dataScopeAnnotationMethod.enabled()) {
                continue;
            }
            if (method.getName().equals(methodName)) {
                log.debug("方法 where:{}  mappedStatementId:{}", where, mappedStatementId);
                return dataScopeFilter(where, dataScopeAnnotationMethod);
            }
        }
        return where;
    }


    /**
     * 构建过滤条件
     *
     * @param where 当前查询条件
     * @return 构建后查询条件
     */
    public static Expression dataScopeFilter(Expression where,DataScope dataScope) {
        // 获取用户信息
        LoginUserInfo currentUser = SpringUtil.getBean(TblUserService.class).getLoginUser();
        Long deptId = currentUser.getDeptId();

        // 获取所有角色信息
        List<TblRole> infos = SpringUtil.getBean(TblRoleService.class).listRoles(currentUser.getUserId());
        // 获取数据权限
        List<Integer> scopeTypeList = infos.stream().map(TblRole::getPermissionScopeType)
                .distinct().toList();
        // 如果数据权限存在1 跳过
        if (scopeTypeList.contains(1)) {
            return where;
        }

        log.debug("角色：{} 数据权限：{}", infos, scopeTypeList);

        //定义条件
        Expression expression = null;
        String tableAlias = dataScope.tableAlias();
        String deptScopeName = dataScope.deptScopeName();
        String oneselfScopeName = dataScope.oneselfScopeName();
        for (TblRole info : infos) {
            Integer permissionScopeType = info.getPermissionScopeType();

            /**
             * 构造部门in表达式。
             */
            InExpression deptIdInExpression = null;

            EqualsTo equalsTo = null;
            /**
             * 2 指定的自定义部门 数据权限
             * 3 当前登录人的所属部门
             * 4 部门以及以下
             * 5 进本人数据权限
             */
            switch (permissionScopeType) {
                case 2:
                    List<Long> deptList = info.getPermissionScope().getDeptIds();
                    deptIdInExpression = buildDataScopeInExpression(tableAlias, deptScopeName, deptList);
                    break;
                case 3:
                    deptIdInExpression = buildDataScopeInExpression(tableAlias, deptScopeName, List.of(deptId));
                    break;
                case 4:
                    // TODO 本部门以及以下
                    List<Long> longs = SpringUtil.getBean(TblDeptService.class).listParentChildDeptIds(deptId);
                    List<Long> deptPer = new ArrayList<>();
                    deptPer.add(deptId);
                    if (CollUtil.isNotEmpty(longs)) {
                        deptPer.addAll(longs);
                    }
                    deptIdInExpression = buildDataScopeInExpression(tableAlias, deptScopeName, deptPer);
                    break;
                case 5:
                    equalsTo = buildDataScopeEqualsTo(tableAlias, oneselfScopeName, StpUtil.getLoginIdAsLong());
                    break;
                default:
                    throw new GlobalException("暂不支持该数据权限类型");
            }
            /**
             * expression为null第一次循环
             * expression不为null，后面循环
             *
             * 1. expression为null，deptIdInExpression不为null，则expression=deptIdInExpression
             * 2. expression不为null，deptIdInExpression不为null，则expression=expression Or deptIdInExpression
             * 3. expression不为null，deptIdInExpression为null，则expression=expression Or equalsTo
             */
            if (expression == null && deptIdInExpression != null) {
                expression = deptIdInExpression;
            } else if (expression == null && equalsTo != null) {
                expression = equalsTo;
            } else if (deptIdInExpression != null) {
                expression = new OrExpression(expression, deptIdInExpression);
            } else if (equalsTo != null) {
                expression = new OrExpression(expression, equalsTo);
            }
        }
        log.debug("构建后查询条件：{}\n", expression);
        if (expression == null) {
            return where;
        }
        return new AndExpression(where, new ParenthesedExpressionList<>(expression));
    }

    /**
     * @param tableAlias       表别名
     * @param deptScopeName    部门限制范围的字段名称
     * @param dataScopeDeptIds 数据权限部门ID集合
     * @return
     */
    private static InExpression buildDataScopeInExpression(String tableAlias, String deptScopeName, List<Long> dataScopeDeptIds) {
        InExpression deptIdInExpression = new InExpression();
        ExpressionList deptIds = new ExpressionList<Expression>(dataScopeDeptIds.stream().map(LongValue::new).collect(Collectors.toList()));
        // 设置左边的字段表达式，右边设置值。
        deptIdInExpression.setLeftExpression(buildColumn(tableAlias, deptScopeName));
        deptIdInExpression.setRightExpression(new ParenthesedExpressionList<Expression>(deptIds));
        return deptIdInExpression;
    }

    /**
     * 构建本人数据权限表达式
     *
     * @param tableAlias       表名
     * @param oneselfScopeName 本人限制范围的字段名称
     * @param userId           本人id
     * @return
     */
    private static EqualsTo buildDataScopeEqualsTo(String tableAlias, String oneselfScopeName, Long userId) {
        EqualsTo oneselfEqualsTo = new EqualsTo();
        oneselfEqualsTo.withLeftExpression(buildColumn(tableAlias, oneselfScopeName));
        oneselfEqualsTo.setRightExpression(new LongValue(userId));
        return oneselfEqualsTo;
    }

    /**
     * 构建Column
     *
     * @param tableAlias 表别名
     * @param columnName 字段名称
     * @return 带表别名字段
     */
    public static Column buildColumn(String tableAlias, String columnName) {
        if (StrUtil.isNotBlank(tableAlias)) {
            columnName = tableAlias + "." + columnName;
        }
        return new Column(columnName);
    }
}
